// $Id$

/**
  * Paginator of Drupal feeds
  * 
  * Usage:
  * $('#my-element').go_paginator({pageSize: 5, service: 'my_service.my_function', feeds: [feed1, feed2], fields: ['nid', 'title', 'type']});
  * 
  * Sample JSON data:
  * {offset: 5, count: 24, items: [
  *   ['http://www.apple.com/', 'Environmental zone cuts city pollution', 10, 0, 'news'],
  *   ['http://www.apple.com/', 'Danish farmers to grow GM crops', 5, 0, 'news']
  * ]}
  * 
  * Try to hook in on events instead of overriding the functions. I.e. $('#my-element').bind('construct_items', myHandler); 
  * instead of $('#my-element').go_paginator({onConstructListItems: myHandler});
  * 
  * 
  * @param int pageSize               Number of items per page, default 10
  * @param string service             Which drupal service to fetch data from
  * @param array feeds                Which feeds to fetch data from
  * @param array fields               Which fields to reeturn
  * @param int currentPage            The page to display initially
  * @param int maxCacheTime           The number of seconds the page cache will be kept. Set to 0 to not use the cache. 
  * @param string archiveUrl          URL to the archive
  * @param string archiveText         Linktext to the archive link
  * @param string nextText            Linktext for the next-link
  * @param string previousText        Linktext for the previous-link
  * @param function onAnimateNext     Function that animates to the next page. Event: animate_next
  * @param function onAnimatePrevious Function that animates to the previous page. Event: animate_previous
  * @param function onLoadPage        Function that loads a page. Event: load_page
  * @param function onLoading         Function that tells the user a page is loading. Event: loading
  * @param function onLoad            Function that tells the user the page is done loaded. Event: loaded
  * @param function onGotoPage        Function goes to a certain page, will trigger either load_page or render_page. Event: goto_page
  * @param function onRenderPage      Function renders a page and animates to it, will trigger animate_next/_previous and construct_items. Event: render_page
  * @param function onConstruct       Function will create initial elements like wrapper, next and previous-links. Event: construct
  * @param function onConstructListItems  Function will create the links of a page. Event: construct_items
  * @uses jQuery
  * @uses Drupal
  * @author Joel Arvidsson <joel@above.se>
  */

(function($){
$.fn.go_paginator = function(userSettings)
{
  var onAnimate = function (direction)
  {
    var wrap = $(settings.container).find('.listing-wrapper').stop(), 
      curr = $(settings.container).find('.listing.current').stop(), 
      anim = $(settings.container).find('.listing.animation').stop(), 
      width= direction*wrap.width();
    
    wrap.animate({height: anim.height() + 'px'}, 200, 'swing');
    curr.css({
      left: 0,
      right: 0
    }).animate({ 
      left: -1*width,
      right: width
    }, 300, 'swing' );
    anim.css({
      left: width,
      right: -1*width
    }).show().animate({ 
      left: 0,
      right: 0
    }, 300, 'swing', function()
      {
        anim.removeClass('animation').addClass('current');
        curr.hide().removeClass('current').addClass('animation');
      }
    );
  };
  
  var settings = $.extend(
    {
      maxCacheTime: 10 * 60 * 60, //seconds
      pageSize: 10,
      pageCount: 0,
      pageMax: 15,
      currentPage: 0,
      gotoPage: 0,
      jsonUrl: null, 
      archiveText: 'Archive',
      archiveUrl: null, 
      nextText: 'Next',
      showPermalink: undefined,
      onAnimateNext: function(evt)
        {
          onAnimate(1);
          evt.preventDefault();
        }, 
      previousText: 'Previous',
      onAnimatePrevious: function(evt)
        {
          onAnimate(-1);
          evt.preventDefault();
        },
      onLoadPage: function(evt, pageNr)
        {
          if (pageNr > settings.pageMax) {
            $(settings.container).triggerHandler('loaded');
            return;
          }
          var params = [
            settings.fields, 
            pageNr * settings.pageSize, 
            settings.pageSize
          ];
          if (typeof settings.feeds !== 'undefined' && typeof settings.filters !== 'undefined') {
            params = [
              settings.feeds, 
              settings.filters
            ].concat(params);
          }
          Drupal.service(settings.service, 
            params,
            function(data, err, code)
            {
              data.page = data.offset/settings.pageSize;
              data.pageCount = (data.count ? Math.ceil(data.count/settings.pageSize) : 0);
              
              if(settings.pageCount != data.pageCount)
              {
                settings.pageCache = {};
                settings.pageCount = data.pageCount;
                $(settings.container).triggerHandler('num_pages_changed');
              }
              settings.setPageCache(data);
              
              if(data.page == settings.gotoPage)
              {
                $(settings.container).triggerHandler('loaded');
                return $(settings.container).triggerHandler('render_page', [data]);
              }
              return $(this);
            }, 1
          );
        },
      onLoading: function()
        {
          $(this).find('.loading-indicator').show();
          $(this).find('.listing.current').css('opacity', .5);
        },
      onLoaded: function(evt, data)
        {
          $(this).find('.loading-indicator').hide();
          $(this).find('.listing.current').css('opacity', 1);
        },
      onGotoPage: function(evt, pageNr)
        {
          if(pageNr < 0)
          {
            pageNr = settings.pageCount-1;
          }
          else if(settings.pageCount <= pageNr)
          {
            pageNr = 0;
          }
          
          settings.gotoPage = pageNr;
          if(data = settings.getPageCache(pageNr))
          {
            return $(this).triggerHandler('render_page', [data]);
          }
          else
          {
            $(this).triggerHandler('loading');
            return $(this).triggerHandler('load_page', [pageNr]);
          }
        },
      onRenderPage: function(evt, data)
        {
          if(!data) { return; }
          $(this).triggerHandler('construct_items', [data]);
          $(this).find('.pages .selected').removeClass('selected');
          $(this).find('.pages li:eq('+data.page+')').addClass('selected');
          $(this).triggerHandler('loaded');
          if(data.page > settings.currentPage)
          {
            $(this).triggerHandler('animate_next');
          }
          else if(data.page < settings.currentPage)
          {
            $(this).triggerHandler('animate_previous');
          }
          else
          {
            var wrap = $(this).find('.listing-wrapper').stop(), 
              curr = $(this).find('.listing.current').stop(), 
              anim = $(this).find('.listing.animation').stop();
            wrap.height(anim.height());
            anim.css({left: 0, right: 0}).show().removeClass('animation').addClass('current');
            curr.hide().removeClass('current').addClass('animation');
          }
          settings.currentPage = settings.gotoPage = data.page;
          
          if(settings.maxCacheTime > 0)
          {
            //Lazy load next and previous page if not cached or nonexisting
            if((data.page+1) < data.pageCount && !settings.getPageCache(data.page+1))
            {
              $(this).triggerHandler('load_page', [data.page+1]);
            }
            if((data.page-1) >= 0 && !settings.getPageCache(data.page-1))
            {
              $(this).triggerHandler('load_page', [data.page-1]);
            }
          }
        },
      onConstruct: function()
        {
          var nav = $('<div class="paginator-navigation"><ul class="pages"/></div>');
          $(this).append(nav);
          nav.append($('<a class="previous">' + settings.previousText + '</a>').click(function()
          {
            $(settings.container).triggerHandler('goto_page', settings.gotoPage - 1);
          }));
          if(settings.archiveUrl)
          {
            nav.append('<a href="' + settings.archiveUrl + '" class="archive"><strong>' + settings.archiveText + '</strong></a>');
          }
          nav.append($('<a class="next">' + settings.nextText + '</a>').click(function(evt)
          {
            $(settings.container).triggerHandler('goto_page', settings.gotoPage + 1);
          }));
          $(this).append($('<div class="listing-wrapper"><ul class="listing current" style="position:absolute;top:0;left:0;right:0;"/><ul class="listing animation" style="position:absolute;top:0;left:0;right:0;"/><div class="loading-indicator" style="display: none; position: absolute; top: 0; left: 0; right: 0; bottom: 0;"><strong>Loading</strong></div></div>').css({position: 'relative', overflow: 'hidden'}));
          },
      onConstructPages: function()
        {
          var list = $(this).show().find('.pages'),
            pageNr = list.find('li').length, item;
          
          if (settings.pageCount === 0) {
            list.hide();
          } else {
            list.show().find('li:last').removeClass('last');
          
            for(pageNr++; pageNr <= settings.pageCount && pageNr <= settings.pageMax; pageNr++)
            {
              item = list.append($('<li />').append($('<a>'+pageNr+'</a> ').click(function(evt){
                $(settings.container).triggerHandler('goto_page', [parseInt($(this).text(), 10)-1]);
              })));
            }
            for(pageNr--; pageNr > settings.pageCount; pageNr--)
            {
              list.find('li:last').remove();
            }
            list.find('li:first').addClass('first');
            list.find('li:last').addClass('last');
          }
        },
      onConstructListItems: function(evt, data)
        {
          var items = data.items, 
            list = $(this).find('.listing.animation');
          
          if (settings.showPermalink === undefined) {
            settings.showPermalink = function(evt) {
              var el = $(this).parent('li');
              if(el.hasClass('expanded'))
              {
                el.removeClass('expanded');
                settings.updateWrapperHeight();
              }
              else
              {
                $(settings.container).triggerHandler('show_item_description', [$.data(el.get(0), 'nid')]);
              }
            };
          }
          
          for(var el, item, i = list.find('li').length; i < items.length; i++)
          {
            item = list.append($('<li></li>').append($('<a class="permalink"></a>').click(settings.showPermalink)));
          }
          list.find('li:lt(' + (items.length) + ')').show();
          list.find('li:gt(' + (items.length - 1) + ')').hide();
          
          for(i = 0; i < items.length; i++)
          {
            item = items[i];
            el = list.find('li:eq(' + i + ')');
            $.data(el.get(0), 'nid', item[0]);
            el.addClass(item[2])
              .find('.permalink').text(item[1]);
          }
          list.find('li:first').addClass('first');
          list.find('li:eq(' + (items.length - 1) + ')').addClass('last');
        }, 
      onShowItemDescription: function(evt, nid)
        {
          settings.showItemDescription = nid;
          var item = settings.getItemByNid(nid);
          if(!item)
          {
            return;
          }
          if(!item.find('.description').length)
          {
            item.append($('<div class="description"><a class="read-more"></a><div class="loading-indicator"><strong>Loading</strong></div></div>'));
          }
          $(this).find('.listing li.expanded').removeClass('expanded');
          item.addClass('expanded').find('.description').find('.read-more').hide().end().find('.loading-indicator').show();
          settings.updateWrapperHeight();
          Drupal.service('node.get', 
            [ nid, ['nid', 'teaser'] ],
            function(data, err, code)
            {
              if(data.nid == settings.showItemDescription)
              {
                item.find('.description').find('.read-more').attr('href', Drupal.settings.basePath + 'js-node-link/'+ data.nid).html(data.teaser.length ? data.teaser.replace(/<\/?[^>]+>/gi, '') : Drupal.t('Click to read more...')).show().end().find('.loading-indicator').hide();
                settings.updateWrapperHeight();
              }
            }, 1
          );
        }, 
      getItemByNid: function(nid)
        {
          var items = $(settings.container).find('.listing.current li');
          for(var i = 0; i < items.length; i++)
          {
            if($.data(items.get(i), 'nid') == nid)
            {
              return $(items.get(i));
            }
          }
        },
      updateWrapperHeight: function()
        {
          //Yes this might not be the prettiest ever...
          $(settings.container).find('.listing-wrapper').height($(settings.container).find('.listing.current').height());
        }
    }, 
    userSettings, 
    {
      container: this, 
      pageCache: {}, 
      getPageCache: function (pageNr)
        {
          if(!settings.maxCacheTime || !settings.pageCache['page_' + pageNr] || settings.pageCache['page_' + pageNr].expires <= new Date())
          {
            return;
          }
          return settings.pageCache['page_' + pageNr];
        }, 
      setPageCache: function (data)
        {
          data.expires = new Date();
          data.expires.setSeconds(data.expires.getSeconds() + settings.maxCacheTime);
          settings.pageCache['page_' + data.page] = data;
          return $(this);
        }
    }
  );
  
  this.bind('animate_next', settings.onAnimateNext);
  this.bind('animate_previous', settings.onAnimatePrevious);
  this.bind('load_page', settings.onLoadPage);
  this.bind('loaded', settings.onLoaded);
  this.bind('loading', settings.onLoading);
  this.bind('goto_page', settings.onGotoPage);
  this.bind('render_page', settings.onRenderPage);
  this.bind('construct', settings.onConstruct);
  this.bind('num_pages_changed', settings.onConstructPages);
  this.bind('construct_items', settings.onConstructListItems);
  this.bind('show_item_description', settings.onShowItemDescription);
  
  $.data($(this).get(0), 'settings', settings);
  
  this.triggerHandler('construct');
  this.triggerHandler('goto_page', [settings.currentPage]);


  return $(this);
};
})(jQuery);